/**
 * Copyright (c) 2015-2030 by Beijing Yanlight Co., Ltd.
 * All rights reserved.
 */
package com.yanlight.support;

import com.yanlight.Constants;
import com.yanlight.exception.ErrorType;
import com.yanlight.exception.TradeException;
import com.yanlight.support.alipay.AlipayConstants;
import com.yanlight.support.alipay.util.AlipayUtil;
import com.yanlight.support.wx.WxConstants;
import com.yanlight.support.wx.util.WxUtil;
import com.yanlight.util.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.beetl.ext.servlet.ServletGroupTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author haolw
 * @time 2017/06/27
 */
public class PretreatmentFilter implements Filter {
    private Logger logger = LoggerFactory.getLogger(getClass());
    private final String ASSETS_PREFIX = "/assets/**";
    private final String NO_WAY_PAY = "/WEB-INF/views/noWayPay.html";
    private final String SPECIAL_DEV_KEY = "6b26116840814558a9b3ed6e915337a8";
    private List<String> callbacks;
    private List<String> noAuthBrowses;
    private List<String> devList;
    private PathMatcher pathMatcher;
    private DefaultServletHttpRequestHandler resourceHandler;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String servletPath = request.getServletPath();
        String contextPath = request.getContextPath();
        request.setAttribute("ctx", contextPath);
        boolean process = true;
        try {
            if (assets(servletPath)) {
                resourceHandler.handleRequest(request, response);
                return;
            } else {
                if (!callback(servletPath)) {
                    if (!noAuthBrowse(servletPath)) {
                        if (WebUtil.isWxBrowse(request)) {
                            process = processWx(request, response);
                        } else if (WebUtil.isAlipayBrowse(request)) {
                            process = processAlipay(request, response);
                        } else {
                            process = false;
                            ServletGroupTemplate.instance().render(NO_WAY_PAY, request, response);
                            return;
                        }
                    }
                    if (devApi(servletPath)) {
                        process = checkDevToken(request, response);
                    }
                }
            }
            if (process) {
                chain.doFilter(request, response);
            }
        } catch (Exception e) {
            doException(request, response, e);
        } finally {
            reset();
        }
    }

    protected boolean checkDevToken(HttpServletRequest request, HttpServletResponse response) {
        String key = request.getHeader(Constants.TOKEN_NAME);
        if (StringUtils.isBlank(key)) {
            render(response, ErrorType.NO_SERVICE);
            return false;
        }
        if (!DevTokenUtil.exists(key)) {
            render(response, ErrorType.INVALID_TOKEN);
            return false;
        }

        // 排除1.0版本，共享乐园的校验，1.0未添加校验逻辑
        if (!SPECIAL_DEV_KEY.equals(key)) {
            String secret = DevTokenUtil.getAndResolve(key).getSecret();
            boolean process = DevSignatureUtil.verify(request, secret);
            if (!process) {
                render(response, ErrorType.SIGNATURE_ERROR);
                return false;
            }
        }
        DevTokenHolder.save(key);
        return true;
    }

    protected boolean processWx(HttpServletRequest request, HttpServletResponse response) {
        String appId = WxConstants.appId;
        String openid = CookieUtil.getValue(request, Constants.WX_OPENID);
        if (StringUtils.isBlank(openid)) {
            String callback = WxConstants.getCallbackUrl();
            WxUtil.userAuthorizeBase(request, response, appId, callback);
            return false;
        }
        return true;
    }

    protected boolean processAlipay(HttpServletRequest request, HttpServletResponse response) {
        String appId = AlipayConstants.appId;
        String userId = CookieUtil.getValue(request, Constants.ALIPAY_USER_ID);
        if (StringUtils.isBlank(userId)) {
            String callback = AlipayConstants.getMemberAuthCallbackUrl();
            AlipayUtil.authorizeBase(request, response, appId, callback);
            return false;
        }
        return true;
    }

    /**
     * 判断是否为静态资源
     *
     * @param path
     * @return
     */
    protected boolean assets(String path) {
        return match(ASSETS_PREFIX, path);
    }

    /**
     * 判断是否为回调
     *
     * @param path
     * @return
     */
    protected boolean callback(String path) {
        return match(callbacks, path);
    }

    /**
     * 判断是否为回调
     *
     * @param path
     * @return
     */
    protected boolean devApi(String path) {
        return match(devList, path);
    }

    /**
     * @param path
     * @return
     */
    protected boolean noAuthBrowse(String path) {
        return match(noAuthBrowses, path);
    }

    private boolean match(String pattern, String path) {
        return pathMatcher.match(pattern, path);
    }

    private boolean match(List<String> patterns, String path) {
        for (String pattern : patterns) {
            if (match(pattern, path)) {
                return true;
            }
        }
        return false;
    }

    /**
     * @param request
     * @param response
     * @param e
     */
    protected void doException(HttpServletRequest request, HttpServletResponse response, Exception e) {
        ErrorType errorType = null;
        Throwable rootException = ExceptionUtils.getRootCause(e);
        String extMsg = null;
        if (rootException instanceof TradeException) {
            TradeException ex = (TradeException) rootException;
            errorType = ex.getErrorType();
            extMsg = ex.getMessage();
            logException(rootException, errorType);
        } else {
            e.printStackTrace();
            errorType = ErrorType.SYSTEM_ERROR;
        }
        if (WebUtil.apiRequest(request)) {
            Integer code = errorType.getCode();
            String msg = errorType.getZhCnMsg();
            if (StringUtils.isNotBlank(extMsg)) {
                msg = msg.concat(":").concat(extMsg);
            }
            WebUtil.renderJson(response, ResponseUtil.fail(code, msg));
            return;
        } else {
            if (e.getMessage().startsWith("Could not resolve view")) {
                response.setStatus(404);
            } else {
                throw new RuntimeException(e);
            }
        }
    }

    private void render(HttpServletResponse response, ErrorType errorType) {
        Integer code = errorType.getCode();
        String msg = errorType.getZhCnMsg();
        WebUtil.renderJson(response, ResponseUtil.fail(code, msg));
    }

    /**
     * @param throwable
     * @param errorType
     */
    protected void logException(Throwable throwable, ErrorType errorType) {
        String className = throwable.getStackTrace()[1].getClassName();
        String methodName = throwable.getStackTrace()[1].getMethodName();
        int lineNumbe = throwable.getStackTrace()[1].getLineNumber();
        String nowTime = DateTimeUtil.getNowTime();
        Integer code = errorType.getCode();
        String enMsg = errorType.getEnMsg();
        String zhCnMsg = errorType.getZhCnMsg();
        String extMsg = throwable.getMessage();
        if (StringUtils.isNotBlank(extMsg)) {
            zhCnMsg = zhCnMsg.concat(":").concat(extMsg);
        }
        String classLog = "{} exception at:{}.{}:{}";
        String typeLog = "{} errorType:{code:{},enMsg:{},cnMsg:{}}";
        logger.error(classLog, nowTime, className, methodName, lineNumbe);
        logger.error(typeLog, nowTime, code, enMsg, zhCnMsg);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        ServletContext context = filterConfig.getServletContext();
        pathMatcher = new AntPathMatcher();
        resourceHandler = new DefaultServletHttpRequestHandler();
        resourceHandler.setServletContext(context);
        callbacks = new ArrayList<String>(3);
        callbacks.add(WxConstants.authCallbackUrl);
        callbacks.add(AlipayConstants.memberAuthCallbackUrl);
        callbacks.add("/order/notifyPay/{companyId}/{payType}");

        noAuthBrowses = new ArrayList<String>(1);
        noAuthBrowses.add("/order/providerSignGrant");
        noAuthBrowses.add("/order/unifiedPlace");
        noAuthBrowses.add("/wx/getOpenId");

        noAuthBrowses.add("/ad/redPacket");

        noAuthBrowses.add("/ad/awardList");
        noAuthBrowses.add("/ad/awardResult");
        noAuthBrowses.add("/ad/clickCount");
        noAuthBrowses.add("/ad/wheelView");
        noAuthBrowses.add("/ad/enableTimes");
        noAuthBrowses.add("/ad/testSendRedPacket");


        noAuthBrowses.add("/code/getCode");
        noAuthBrowses.add("/apply/money");
        noAuthBrowses.add("/apply/info");
        noAuthBrowses.add("/active/applyMoney");

        noAuthBrowses.add("/test/push");
        noAuthBrowses.add("/test/multiPush");

        devList = new ArrayList<String>(1);
        devList.add("/order/unifiedPlace");
    }

    @Override
    public void destroy() {

    }

    private void reset() {
        DevTokenHolder.reset();
    }

}
